// 15 面向对象程序设计
/**
 * 面向对象程序设计基于三个基本概念：数据抽象、继承和动态绑定。第7章已经介绍了数据抽象的知识，本章将介绍继承和动态绑定。
 * 继承和动态绑定对程序的编写有两方面的影响：一是我们可以更容易地定义与其他类相似但不完全相同的新类；二是在使用这些彼此相似的类编写程序时，我们可以在一定程度上忽略掉它们的区别。
 * 在很多程序中都存在着一些相互关联但是有细微差别的概念。
 * 例如，书店中不同书籍的定价策略可能不同：有的书籍按原价销售，有的则打折销售。有时，我们给那些购买书籍超过一定数量的顾客打折；另一些时候，则只对前多少本销售的书籍打折，之后就调回原价，等等。
 * 面向对象的程序设计（OOP）适用于这类应用。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include "../Chapter13/13.5.cc" // 不能编译，因为重复定义的main函数
#include "../Chapter12/12.1.6.cc" // 不能编译，因为重复定义的main函数
#include <functional>

class Base
{
protected:
    int prot_mem; // protected成员
};
class Sneaky : public Base{
    friend void clobber(Sneaky &); // 能访问Sneaky::prot_mem
    friend void clobber(Base &);   // 不能访问Base::prot_mem
    int j;                         // j默认是private
};
// 正确，clobber能访问Sneaky对象的private和protected成员
void clobber(Sneaky &s) { s.j = s.prot_mem = 0; }
// 错误，clobber不能访问Base的protected成员
//void clobber(Base &b) { b.prot_mem = 0; }

class Base1
{
public:
    void pub_mem();
protected:
    int prot_mem;
private:
    char priv_mem;
};
struct Pub_Derv : public Base1
{
    // 正确，派生类能访问基类的protected成员
    int f() { return prot_mem; }
    // 错误，private成员对于派生类是不可访问的
    //char g() { return priv_mem; }
};
struct Priv_Derv : private Base1
{
    // 派生列表中的private不影响派生类对基类protected成员的访问权限
    int f1() const { return prot_mem; }
};

class Base2
{
public:
    size_t size() const { return n; }
protected:
    size_t n;
};
class Derived2 : private Base2 // 注意是私有继承
{
public:
    // 保持对象尺寸相关的成员的访问级别
    using Base2::size;
protected:
    using Base2::n;
};

int main()
{
    // 15.5 访问控制与继承
    // 每个类分别控制自己的成员初始化过程（参见15.2.2节，第531页），与之类似，每个类还分别控制着其成员对于派生类来说是否可访问（accessible）。

    // 受保护的成员
    // protected说明符可以看做是public和private中和后的产物：
    // · 和私有成员类似，受保护的成员对于类的用户来说是不可访问的。
    // · 和公有成员类似，受保护的成员对于派生类的成员和友元来说是可访问的。此外，protected还有另外一条重要的性质。
    // · 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。派生类对于一个基类对象中的受保护成员没有任何访问特权。

    // 共有、私有和受保护继承
    // 某个类对其继承而来的成员的访问权限受到两个因素影响：一是在基类中该成员的访问说明符，二是在派生类的派生列表中的访问说明符。
    // 派生访问说明符对于派生类的成员（及友元）能否访问其直接基类的成员没什么影响。对基类成员的访问权限只与基类中的访问说明符有关。
    // 派生访问说明符的目的是控制派生类用户（包括派生类的派生类在内）对于基类成员的访问权限：
    Pub_Derv d1;
    Priv_Derv d2;
    d1.pub_mem(); // 正确，pub_mem在派生类中是public的
    //d2.pub_mem(); // 错误，pub_mem在派生类中是private的。派生类对象因为派生列表的派生访问说明符被设置为private，所以访问不了基类的public成员，但这又有什么用？

    // 派生类向基类转换的可访问性
    // 假定D继承自B：
    // · 只有当D公有地继承B时，用户代码才能使用派生类向基类的转换；如果D继承B的方式是受保护的或者私有的，则用户代码不能使用该转换。
    // · 不论D以什么方式继承B，D的成员函数和友元都能使用派生类向基类的转换；派生类向其直接基类的类型转换对于派生类的成员和友元来说永远是可访问的。
    // · 如果D继承B的方式是公有的或者受保护的，则D的派生类的成员和友元可以使用D向B的类型转换；反之，如果D继承B的方式是私有的，则不能使用。
    // 对于代码中的某个给定节点来说，如果基类的公有成员是可访问的，则派生类向基类的类型转换也是可访问的；反之则不行。

    // ***关键概念：类的设计与受保护的成员***
    // 不考虑继承的话，我们可以认为一个类有两种不同的用户：普通用户和类的实现者。
    // 其中，普通用户编写的代码使用类的对象，这部分代码只能访问类的公有（接口）成员；实现者则负责编写类的成员和友元的代码，成员和友元既能访问类的公有部分，也能访问类的私有（实现）部分。
    // 如果进一步考虑继承的话就会出现第三种用户，即派生类。基类把它希望派生类能够使用的部分声明成受保护的。普通用户不能访问受保护的成员，而派生类及其友元仍旧不能访问私有成员。
    // 和其他类一样，基类应该将其接口成员声明为公有的；同时将属于其实现的部分分成两组：一组可供派生类访问，另一组只能由基类及基类的友元访问。
    // 对于前者应该声明为受保护的，这样派生类就能在实现自己的功能时使用基类的这些操作和数据；对于后者应该声明为私有的。

    // 友元与继承
    // 就像友元关系不能传递一样（参见7.3.4节，第250页），友元关系同样也不能继承。基类的友元在访问派生类成员时不具有特殊性，类似的，派生类的友元也不能随意访问基类的成员：

    // 改变个别成员的可访问性
    // 有时我们需要改变派生类继承的某个名字的访问级别，通过使用using声明（参见3.1节，第74页）可以达到这一目的：
    // 因为Derived使用了私有继承，所以继承而来的成员size和n（在默认情况下）是Derived的私有成员。
    // 然而，我们使用using声明语句改变了这些成员的可访问性。改变之后，Derived的用户将可以使用size成员，而Derived的派生类将能使用n。
    // 派生类只能为那些它可以访问的名字提供using声明。

    // 默认的继承保护级别
    // 默认情况下，使用class关键字定义的派生类是私有继承的；而使用struct关键字定义的派生类是公有继承的：
    // 人们常常有一种错觉，认为在使用struct关键字和class关键字定义的类之间还有更深层次的差别。
    // 事实上，唯一的差别就是默认成员访问说明符及默认派生访问说明符；除此之外，再无其他不同之处。
    // 一个私有派生的类最好显式地将private声明出来，而不要仅仅依赖于默认的设置。显式声明的好处是可以令私有继承关系清晰明了，不至于产生误会。

    

    return 0;
}